package com.artifex.mupdf.viewer;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.CountDownTimer;
import android.os.IBinder;
import android.os.PowerManager;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;

import com.artifex.mupdf.fitz.Quad;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Tasks;
import com.google.mlkit.samples.nl.translate.AppExecutor;
import com.google.mlkit.samples.nl.translate.MainHandler;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

import kotlin.Triple;

public class TTSService extends Service
        implements TextToSpeech.OnInitListener, SharedPreferences.OnSharedPreferenceChangeListener {
    private static final String TAG = "TTSService";
    private final int NOTIFICATION_ID = 1001;
    private final String CHANNEL_ID = "MuPDF_TTS_Channel";

    private TextToSpeech tts;
    private final AtomicBoolean ttsInitialized = new AtomicBoolean(false);

    private boolean pending = false;
    private TTSDocument core;
    private int currentPage = 0;
    private List<Triple<SettingsItem, String, Quad[][]>> currentPageSentences;
    private int currentSentenceIndex = 0;
    private TTSCallback callback;
    private final IBinder binder = new TTSBinder();
    private boolean shouldContinueReading = true;
    private CountDownTimer countdownTimer;
    private SharedPreferences prefs;

    public class TTSBinder extends Binder {
        TTSService getService() {
            return TTSService.this;
        }
    }

    public interface TTSCallback {
        void onSegmentStart(Quad[][] boxes);

        void onSegmentDone(int page, int segmentIndex);

        void onPageChanged(int newPage);

        void onTTSInitialized(boolean success);

        void onTTSStopped();

        void onError(String message);

        void onDocCreated(TTSDocument document);

        void onQuit();
    }

    BroadcastReceiver audioNoisyReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
                stopReading();
            }
        }
    };

    @Override
    public void onCreate() {
        Log.d("TTSService", "onCreate");
        super.onCreate();
        createNotificationChannel();
        startForeground(NOTIFICATION_ID, createNotification("Preparing TTS..."));

        tts = new TextToSpeech(this, this, "com.google.android.tts");
        registerReceiver(audioNoisyReceiver
                , new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));

    }

    @RequiresApi(api = Build.VERSION_CODES.N)
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("TTSService", "onStartCommand");
        Toast.makeText(getApplicationContext(), "just TTS Warming up", Toast.LENGTH_LONG).show();
        return START_STICKY;
    }

    public void registerOnSharedPreferenceChange(Uri uri) {
        unregisterOnSharedPreferenceChange();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Log.d(TAG, "registerOnSharedPreferenceChangeListener");
            String name = Base64.getUrlEncoder().encodeToString(uri.toString().getBytes(StandardCharsets.UTF_8));
            prefs = getSharedPreferences(name, Context.MODE_PRIVATE);
            prefs.registerOnSharedPreferenceChangeListener(this);
        }
    }

    void unregisterOnSharedPreferenceChange() {
        if (prefs != null) {
            Log.d(TAG, "unregisterOnSharedPreferenceChangeListener");
            prefs.unregisterOnSharedPreferenceChangeListener(this);
            prefs = null;
        }
    }

    public void prepareDoc(Uri docUri) {
        registerOnSharedPreferenceChange(docUri);
        Log.d("TTSService", "preparing document: " + docUri);
        if (docUri != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                if (core == null) {
                    Log.d("TTSService", "create blank new document");
                    newDoc(docUri);
                } else if (docUri.equals(core.uri)) {
                    if (core.destroyed()) {
                        Log.d("TTSService", "the same uri but had been destroyed");
                        core = null;
                        newDoc(docUri);
                    } else {
                        Log.d("TTSService", "the same uri and not destroyed yet, free to go");
                    }
                } else {
                    Log.d("TTSService", "different uri, should create new one");
                    core.destroy();
                    core = null;
                    newDoc(docUri);
                }
            }
        }
        if (core != null && callback != null) callback.onDocCreated(core);
    }

    void newDoc(Uri docUri) {
        stopReading();
        AppExecutor.obtain().submit(() -> {
            try {
                synchronized (TTSDocument.class) {
                    TTSDocument tmp;
                    if (TTSService.this.core != null) {
                        Log.e("TTSService", "something wrong, document had been created");
                        return;
                    }
                    tmp = new TTSDocument(TTSService.this, docUri);
                    TTSService.this.core = tmp;
                }
                MainHandler.obtain().post(() -> {
                    if (callback != null) callback.onDocCreated(core);
                });
            } catch (IOException e) {
                Log.e("TTSService", "create document fail", e);
                MainHandler.obtain().post(() -> {
                    Toast.makeText(TTSService.this, "create document Fail", Toast.LENGTH_LONG).show();
                });
            }
        });
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.d("TTSService", "onBind");
        return binder;
    }

    @Override
    public void onInit(int status) {
        if (status == TextToSpeech.SUCCESS) {
            int result = tts.setLanguage(Locale.getDefault());
            if (result == TextToSpeech.LANG_MISSING_DATA ||
                    result == TextToSpeech.LANG_NOT_SUPPORTED) {
                Log.e(TAG, "TTS Language not supported");
                if (callback != null) {
                    callback.onTTSInitialized(false);
                    callback.onError("TTS Language not supported");
                }
                return;
            }

            ttsInitialized.set(true);
            tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
                @Override
                public void onStart(String utteranceId) {
                    if (callback != null) {
                        callback.onSegmentStart(currentPageSentences.get(currentSentenceIndex).getThird());
                    }
                    updateNotification("Reading page " + (currentPage + 1) +
                            ", sentence " + (currentSentenceIndex + 1));
                }

                @Override
                public void onDone(String utteranceId) {
                    if (callback != null) {
                        callback.onSegmentDone(currentPage, currentSentenceIndex);
                    }
                    if (!shouldContinueReading) return;

                    currentSentenceIndex++;
                    if (currentSentenceIndex < currentPageSentences.size()) {
                        speakNextSentence();
                    } else {
                        // Move to next page
                        currentSentenceIndex = 0;
                        currentPage++;
                        if (callback != null) {
                            callback.onPageChanged(currentPage);
                        }

                        if (core.destroyed()) {
                            stopReading();
                            core = null;
                            return;
                        }
                        if (currentPage >= core.doc.countPages()) {
                            // Reached end of document
                            stopReading();
                            return;
                        }

                        // Load next page text
                        loadPageText(currentPage, ignored -> {
                            if (shouldContinueReading) {
                                speakNextSentence();
                            }
                        });
                    }

                }

                @Override
                public void onError(String utteranceId) {
                    if (callback != null) {
                        callback.onError("Continue Next segment, but TTS error on utteranceId " + utteranceId);
                    }
                    currentSentenceIndex++;
                    if (shouldContinueReading) {
                        speakNextSentence();
                    }
                }
            });

            if (callback != null) {
                callback.onTTSInitialized(true);
            }
            if (pending) {
                speakNextSentence();
            }

        } else {
            Log.e(TAG, "TTS Initialization failed");
            if (callback != null) {
                callback.onTTSInitialized(false);
                callback.onError("TTS Initialization failed");
            }
        }
    }

    public void startReading(int page) {
        acquireWakeLock();
        if (core != null) {
            if (page < core.doc.countPages())
                currentPage = page;
            shouldContinueReading = true;
            if (currentPageSentences == null || currentPageSentences.isEmpty()) {
                loadPageText(currentPage, i -> speakNextSentence());
            } else {
                speakNextSentence();
            }
        }
    }

    public void pauseReading() {
        releaseWakeLock();
        shouldContinueReading = false;
        if (currentPageSentences != null)
            currentPageSentences.clear();
        if (tts != null) {
            tts.stop();
        }
    }

    public void stopReading() {
        releaseWakeLock();
        shouldContinueReading = false;
        if (currentPageSentences != null)
            currentPageSentences.clear();
        if (tts != null) {
            tts.stop();
        }
        currentSentenceIndex = 0;
        if (callback != null) {
            callback.onTTSStopped();
        }
        updateNotification("TTS Stopped");
//        stopPlayback();
    }

    public void setCallback(TTSCallback callback) {
        this.callback = callback;
    }

    public boolean isSpeaking() {
        return tts != null && tts.isSpeaking();
    }

    public int getCurrentPage() {
        return currentPage;
    }

    private void loadPageText(int pageNum, OnSuccessListener<Void> listener) {
        if (core != null && pageNum >= 0 && !core.destroyed() && pageNum < core.doc.countPages()) {
//            Toast.makeText(getApplicationContext(), (pageNum + 1) + " Now loading segments...", Toast.LENGTH_LONG).show();
            core.getSegments(pageNum).continueWithTask(task -> {
                if (task.isSuccessful()) {
                    List<Triple<SettingsItem, String, Quad[][]>> result = task.getResult();
                    currentPageSentences = result;
                    listener.onSuccess(null);
                    return core.translate(result);
                }
                if (task.getException() != null) {
                    Log.e("TTSService", "Segmentation Fail", task.getException());
                    return Tasks.forException(task.getException());
                }
                return Tasks.forException(new Exception("Segmentation Fail"));
            }).addOnSuccessListener(translated -> {
                if (translated == null || translated.isEmpty())
                    return;
                List<Triple<SettingsItem, String, Quad[][]>> result = new ArrayList<>();
                int size = Math.min(currentPageSentences.size(), translated.size());
                for (int i = 0; i < size; i++) {
                    result.add(currentPageSentences.get(i));
                    result.add(translated.get(i));
                }
                currentPageSentences = result;
                currentSentenceIndex = currentSentenceIndex + Math.min(currentSentenceIndex, translated.size());
                Log.i("TTSService", "Translated accepted");

            }).addOnFailureListener(
                    e -> Log.e("TTSService", "translation Fail", e)
            );
        } else {
            if (currentPageSentences != null)
                currentPageSentences.clear();
            if (callback != null) {
                callback.onError("Invalid page number: " + pageNum);
            }
        }
    }

    private void speakNextSentence() {
        if (currentPageSentences != null && currentSentenceIndex < currentPageSentences.size()) {
            if (ttsInitialized.get()) {
                pending = false;
                Triple<SettingsItem, String, Quad[][]> tmp = currentPageSentences.get(currentSentenceIndex);
                tts.setLanguage(tmp.getFirst().language);
                tts.setSpeechRate(tmp.getFirst().speed);
                tts.speak(tmp.getSecond(), TextToSpeech.QUEUE_FLUSH, null, currentPage + "-" + currentSentenceIndex);
            } else {
                if (callback != null) {
                    callback.onError("Waiting for TTS init...");
                }
                pending = true;
            }
        } else {
            // Shouldn't normally reach here, but just in case
            stopReading();
        }
    }

    private Notification createNotification(String text) {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("MuPDF - Reading Aloud")
                .setContentText(text)
                .setSmallIcon(android.R.drawable.btn_radio)
                .setColor(Color.BLUE)
                .setOngoing(true)
                .setOnlyAlertOnce(true);

        return builder.build();
    }

    private void updateNotification(String text) {
        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        if (manager != null) {
            manager.notify(NOTIFICATION_ID, createNotification(text));
        }
    }

    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    CHANNEL_ID,
                    "MuPDF TTS",
                    NotificationManager.IMPORTANCE_LOW
            );
            channel.setDescription("MuPDF Text-to-Speech service");
            channel.setShowBadge(false);

            NotificationManager manager = getSystemService(NotificationManager.class);
            if (manager != null) {
                manager.createNotificationChannel(channel);
            }
        }
    }

    @Override
    public void onDestroy() {
        Log.d("TTSService", "onDestroy");
        callback = null;
        if (countdownTimer != null) {
            countdownTimer.cancel();
            countdownTimer = null;
        }
        unregisterOnSharedPreferenceChange();
        shouldContinueReading = false;
        if (tts != null) {
            tts.stop();
            tts.shutdown();
        }
        if (core != null) {
            core.destroy();
            core = null;
        }

        super.onDestroy();
        unregisterReceiver(audioNoisyReceiver);
    }

    // Add permission to AndroidManifest.xml
// <uses-permission android:name="android.permission.WAKE_LOCK" />

    private PowerManager.WakeLock wakeLock;

    private void acquireWakeLock() {
        if (wakeLock == null) {
            PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
            wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                    "MyApp::MyTTSWakeLock");
            wakeLock.acquire(60 * 60 * 1000);
        }
    }

    private void releaseWakeLock() {
        if (wakeLock != null && wakeLock.isHeld()) {
            wakeLock.release();
        }
        wakeLock = null;
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, @Nullable String key) {
        Log.d(TAG, "onSharedPreferenceChanged" + key);
        if (Objects.equals(key, "timer_end_time")) {
            long endTime = sharedPreferences.getLong("timer_end_time", 0);
            Log.d(TAG, "timer: " + endTime);
            if (System.currentTimeMillis() > endTime) {
                sharedPreferences.edit().remove("timer_end_time").apply();
                return;
            }
            // Clear any existing timer
            if (countdownTimer != null) {
                countdownTimer.cancel();
            }

            long durationMillis = endTime - System.currentTimeMillis();
            countdownTimer = new CountDownTimer(durationMillis, 1000) {
                @Override
                public void onTick(long millisUntilFinished) {
                    // The timer is running in the background, no UI update needed here
                }

                @Override
                public void onFinish() {
                    Log.d(TAG, "Time's up");
                    if (callback != null)
                        callback.onQuit();
                    // This is the key part: When the timer finishes, stop the service.
                    stopSelf();
                }
            }.start();
        }
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d("TTSService", "onUnbind");
        return super.onUnbind(intent);
    }
}
